package club.baimahu.pencil.miaosha.service;

import club.baimahu.pencil.annotation.Timer;
import club.baimahu.pencil.base.ServerConstant;
import club.baimahu.pencil.miaosha.domain.Stock;
import club.baimahu.pencil.miaosha.domain.StockOrder;
import club.baimahu.pencil.miaosha.mybatis.MapperFactory;
import club.baimahu.pencil.util.IdWorker;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.stereotype.Service;
import club.baimahu.pencil.miaosha.mapper.*;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 库存服务
 *
 * @author zhanghongjiang
 * @version 1.0
 * @date 2021/9/13 16:30
 */
@Slf4j
@Service
public class StockService {

    @Autowired
    StockMapper stockMapper;

    @Autowired
    StockOrderMapper stockOrderMapper;

    @Autowired
    RedisTemplate<String,String> redisTemplate;

    @Autowired
    DefaultMQProducer defaultMQProducer;

    @Autowired
    IdWorkerService idWorkerService;

    public static final String STOCK_COUNT_KEY_PREFIX = "STOCK_COUNT_KEY_PREFIX_";

    public static final String CREATE_STOCK_ORDER_TOPIC = "CREATE_STOCK_ORDER_TOPIC";

    public static final String DECREASE_STOCK_TOPIC = "DECREASE_STOCK_TOPIC";

    public static final String STOCK_ID_SET = "STOCK_ID_SET";

    @PostConstruct
    public void initStockIdSet(){
        SetOperations<String,String> set =  redisTemplate.opsForSet();
        List<Integer> idList = stockMapper.selectStockIds();
        for(Integer id : idList){
            set.add(STOCK_ID_SET,id.toString());
            Stock stock = stockMapper.getStock(1);
            redisTemplate.opsForValue().set(getStockCountKey(id),String.valueOf(stock.getCount()-stock.getSale()));
        }
    }

//    @Transactional(rollbackFor = Exception.class)
//    public int createOrder(int sid,int userId){
//
//        long m1 = System.currentTimeMillis();
//        // 查询库存
//        Stock stock = getStock(sid);
//
//        long m2 = System.currentTimeMillis();
//
//        // 检查库存数量
//        checkStock(stock);
//
//        long m3 = System.currentTimeMillis();
//
//
//        // 扣减库存,根据版本号更新
//        //decreaseStock(stock);
//
//        //更快扣减,只要库存数量大于0,就减一
//        decreaseStockFast(stock.getId());
//
//        long m4 = System.currentTimeMillis();
//
//        //直接创建订单
//        //createStockOrder(stock);
//
//        //通过消息实现创建订单
//        sendMqCreateOrder(sid, userId, stock);
//
//        long m5 = System.currentTimeMillis();
//
//        log.info("getStock use {} ms ,checkStock use {} ms ,decreaseStock use {} ms ,sendMqCreateOrder use {} ms",m2-m1,m3-m2,m4-m3,m5-m4);
//
//        return 1;
//
//    }

    @Transactional(rollbackFor = Exception.class)
    public int createOrder(int sid,int userId){

        long m1 = System.currentTimeMillis();

        if(!redisTemplate.opsForSet().isMember(STOCK_ID_SET,String.valueOf(sid))){
            log.info("库存id不存在,sid={}",sid);
            throw new RuntimeException("库存id不存在");
        }
        Stock stock = getStock(sid);

        long leftCount = redisTemplate.opsForValue().decrement(getStockCountKey(sid),1);

        if(leftCount < 0){
            redisTemplate.opsForValue().increment(getStockCountKey(sid),1);
            throw new RuntimeException("库存不足");
        }else{
            try {
                String key = idWorkerService.nextId() + "_" + sid;
                Message msg = new Message(DECREASE_STOCK_TOPIC , "", key, String.valueOf(sid).getBytes("utf-8"));
                SendResult sendResult = defaultMQProducer.send(msg);
                log.info("发送MQ消息扣减库存,sendResult = {}" , sendResult.toString());
            } catch (Exception e) {
                log.error("发送MQ消息扣减库存:" + e.getLocalizedMessage());
                e.printStackTrace();
                throw new RuntimeException("MQ消息发送出错");
            }
        }

        long m2 = System.currentTimeMillis();

        sendMqCreateOrder(sid, userId, stock);

        long m3 = System.currentTimeMillis();

        log.info("decreaseStock use {} ms ,sendMqCreateOrder use {} ms",m2-m1,m3-m2);

        return 1;

    }


    // 发送mq消息生成订单
    @Timer
    private void sendMqCreateOrder(int sid, int userId, Stock stock) {
        try {
            String body = sid + ";" + stock.getName() + ";" + userId;
            String key = idWorkerService.nextId() + "";
            Message msg = new Message(CREATE_STOCK_ORDER_TOPIC , "", key, body.getBytes("utf-8"));
            SendResult sendResult = defaultMQProducer.send(msg);
            log.info("发送MQ消息生成订单,sendResult = {}" , sendResult.toString());
        } catch (Exception e) {
            log.error("发送MQ消息生成订单异常:" + e.getLocalizedMessage());
            e.printStackTrace();
            throw new RuntimeException("MQ消息发送出错");
        }
    }

    @Timer
    public Stock getStock(int sid){
        return stockMapper.getStock(sid);
    }


    @Timer
    public void checkStock(Stock stock){
        if(stock == null){
            throw new IllegalArgumentException("stock cannot be null");
        }
        int count = stock.getCount();
        int sale = stock.getSale();

        if(count == sale){
            throw new RuntimeException("all stock sold  , checkStock error  , sid = [" + stock.getId()+"]");
        }
    }



    public void checkStockByCache(int sid){

        int count = getStockCountByCache(sid);

        if(count <= 0 ){
            throw new RuntimeException("all stock sold  , checkStock error  , sid = [" + sid+"]");
        }

    }


    public void decreaseStock(Stock stock){
        if(stock == null){
            throw new IllegalArgumentException("stock cannot be null");
        }
        stock.setSale( stock.getSale() + 1 );
        int affects = stockMapper.updateStock(stock);
        if(affects == 0){
            log.warn("库存扣减失败");
            throw new RuntimeException("stock decrease fail");
        }else{
            log.info("库存扣减成功");
            redisTemplate.delete(getStockCountKey(stock.getId()));
        }
    }

    // 原子扣减库存,不再更新整个stock对象,只增加销售量(where 库存总数 >= 销售量)
    @Timer
    public int decreaseStockFast(int id){
        int affects = stockMapper.decreaseStock(id);
        if(affects == 0){
            log.warn("库存扣减失败");
            throw new RuntimeException("stock decrease fail");
        }else{
            log.info("库存扣减成功");
        }
        return affects;
    }


    public int createStockOrder(Stock stock){
        StockOrder stockOrder = new StockOrder();
        stockOrder.setName(stock.getName());
        stockOrder.setSid(stock.getId());
        stockOrder.setCreateTime(new Date());
        return stockOrderMapper.insertStockOrder(stockOrder);
    }

    public int createStockOrder(StockOrder stockOrder){
        if(stockOrder == null){
            throw new IllegalArgumentException("stockOrder cannot be null");
        }
        return stockOrderMapper.insertStockOrder(stockOrder);
    }


    public String getStockCountKey(int sid){
        return STOCK_COUNT_KEY_PREFIX + sid ;
    }



    public int getStockCountByDB(int sid){
        Stock stock =  getStock(sid);
        return stock.getCount() - stock.getSale();

    }


    public int getStockCountByCache(int sid){

        String key = getStockCountKey(sid);

        String valueString = redisTemplate.opsForValue().get(key);
        int value = -1 ;

        if(valueString == null){
            value  = getStockCountByDB(sid);
            redisTemplate.opsForValue().set(key,String.valueOf(value),1, TimeUnit.MINUTES);
        }

        return value;
    }
}
